﻿//#define TRANSFER_GRAMMAR_FLAGS

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using alib;
using alib.BitArray;
using alib.Character;
using alib.Collections;
using alib.Collections.ReadOnly;
using alib.Debugging;
using alib.Dictionary;
using alib.Enumerable;
using alib.Reflection;
using alib.String;

namespace agree.transfer.old
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[Flags]
	public enum RelMode : byte
	{
		_mask /*		*/ = In | Out | Source | Target,
		None /*			*/ = 0x0,
		In /*			*/ = 0x1,
		Out /*			*/ = 0x2,
		Source /*		*/ = 0x4,
		Target /*		*/ = 0x8,
		SourceIn /*		*/ = Source | In,
		SourceOut /*	*/ = Source | Out,
		TargetIn /*		*/ = Target | In,
		TargetOut /*	*/ = Target | Out,
	};

	public interface ITransferGrammarItem : IIdentity
	{
		TransferGrammar TransferGrammar { get; }
	};

	public interface ITransferRule : ITransferGrammarItem, IInstance
	{
		void ApplyRule(MultiRootTfs inp_rels, MultiRootTfs hyp_rels);
		bool IsUsedInGroups { get; }
		IList<TransferRuleGroup> GroupMembership { get; }
		void AddRuleGroup(TransferRuleGroup trg);
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// This is a transfer grammar (reference) entity, not a runtime-operational/TFS entity
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{ToString(),nq}")]
	public struct TransferRelMode : IEquatable<TransferRelMode>, IComparable<TransferRelMode>
	{
		public readonly GrammarRef tgr;
		public readonly TaggedRelMode tgrm;

		TransferRelMode(GrammarRef tgr, TaggedRelMode tgrm)
		{
			Debug.Assert(tgrm.grm_idx == tgr.ggix);
			this.tgr = tgr;
			this.tgrm = tgrm;
		}
		public TransferRelMode(TransferGrammar tg, TaggedRelMode tgrm)
			: this(tg[tgrm], apply_direction(tg as SrcTgtGrammar, tgrm))
		{
		}
		public TransferRelMode(TransferTfs rtfs, int ix1)
			: this(rtfs.TransferGrammar, rtfs.entries[ix1 - 1].tgrm)
		{
		}
		public TransferRelMode(GrammarRef tgr, RelMode rm)
			: this(tgr, new TaggedRelMode(tgr.ggix, rm))
		{
		}
		public TransferRelMode(TransferGrammar tg, String grammar_key, String in_out)
			: this(tg[grammar_key], in_out.Trim().ParseEnum<RelMode>())
		{
		}
		public static TaggedRelMode apply_direction(MultiRootTfs rtfs, int ix1)
		{
			TransferTfs ttfs = rtfs as TransferTfs;
			SrcTgtGrammar stg = ttfs == null ? null : ttfs.tg as SrcTgtGrammar;
			TaggedRelMode rmg = rtfs.entries[ix1 - 1].tgrm;
			return stg == null ? rmg : apply_direction(stg, rmg);
		}
		public static void set_tag_num(ref TransferRelMode trm, int i_tag)
		{
			trm = new TransferRelMode(trm.tgr, new TaggedRelMode(trm.tgrm.grm_idx, trm.tgrm.rm, i_tag));
		}
		public static TaggedRelMode apply_direction(SrcTgtGrammar stg, TaggedRelMode rm_ggix)
		{
			if (stg != null)
			{
				int grm_idx = rm_ggix.grm_idx;
				if (grm_idx == stg.Source.ggix)
					rm_ggix.rm |= RelMode.Source;
				else if (grm_idx == stg.Target.ggix)
					rm_ggix.rm |= RelMode.Target;
			}
			return rm_ggix;
		}
		public void _validate_consistency()
		{
			var stg = tg as SrcTgtGrammar;
			if (stg == null ?
					(tgrm.rm & (RelMode.Source | RelMode.Target)) != 0 :
					!tgrm.Equals(apply_direction(stg, tgrm)))
				throw new Exception();
		}

		public bool IsSource { get { return (tgrm.rm & RelMode.Source) != 0; } }
		public bool IsTarget { get { return (tgrm.rm & RelMode.Target) != 0; } }
		public bool IsInRel { get { return (tgrm.rm & RelMode.In) != 0; } }
		public bool IsOutRel { get { return (tgrm.rm & RelMode.Out) != 0; } }
		public bool HasDirection { get { return (tgrm.rm & (RelMode.Source | RelMode.Target)) != 0; } }
		public bool IsFlat { get { return (tgrm.rm & (RelMode.In | RelMode.Out)) == RelMode.None; } }

		public Grammar g { get { return tgr.g; } }

		public TransferGrammar tg { get { return tgr.tg; } }

		public bool Equals(TransferRelMode other)
		{
			return tgr.Equals(other.tgr) && tgrm == other.tgrm;
		}
		public override bool Equals(object obj)
		{
			return obj is TransferRelMode && Equals((TransferRelMode)obj);
		}
		public override int GetHashCode()
		{
			return tgr.GetHashCode() ^ tgrm.GetHashCode();
		}
		public int CompareTo(TransferRelMode other)
		{
			int d = this.tgr.CompareTo(other.tgr);
			if (d != 0)
				return d;
			return this.tgrm.CompareTo(other.tgrm);
		}
		public override String ToString()
		{
			if (tgr.tg == null)
				return "(null)";
			return String.Format("{0} rels-{1}", g.Name, tgrm.rm.ToString().ToLower());
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// Binds a single grammar, as loaded into the 'agree' system, as a particular one of the grammars 
	/// in a transfer grammar, a bidirectional mapping
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{ToString(),nq}")]
	public sealed class GrammarRef : IComparable<GrammarRef>
	{
		public GrammarRef(TransferGrammar tg, Grammar g, String key)
		{
			Debug.Assert(key == TransferGrammar.clean_grammar_key(key));

			this.tg = tg;			// ...this transfer grammar.
			this.g = g;
			this.key = key;
		}
		public int ggix { get { return g.grm_idx; } }
		public int tgix { get { return tg.GetGrammarIndex(g); } }
		public readonly TransferGrammar tg;
		public readonly String key;
		public readonly Grammar g;

		public Grammar Grammar { get { return g; } }


		public int CompareTo(GrammarRef other)
		{
			if (this.tg != other.tg)
				throw new InvalidOperationException();
			return this.tgix - other.tgix;
		}

		public override String ToString()
		{
			if (tg == null)
				return "(null)";
			return String.Format("{0} {1}", tg.Name, g.Name);
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// declarative description of a transfer grammar
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class TransferGrammar : identity_named //, IAuthored
	{
		public TransferGrammar(IIdentity prv, String filename)
			: base(prv)
		{
			this.grammar_refs = new GrammarRef[2];
			if (filename != null)
				this.filename = Path.GetFullPath(filename);
		}
		//public AuthorInfo author_info;
		//public override String Name { get { return author_info.Name; } }
		public String Description { get { return ""; } }
		//public String Author { get { return ""; } }
		public String Date { get { return ""; } }

		public String filename;
		protected String get_source_xdl()
		{
			return System.IO.File.ReadAllText(filename).RemoveCommentsCondense();
		}

		public Dictionary<String, int> flag_name_map;
		public int NextFlagIdx { get { return flag_name_map.Count; } }

		public Dictionary<String, int> label_name_map;
		public int NextLabelIdx { get { return label_name_map.Count; } }

		public readonly GrammarRef[] grammar_refs;

		public readonly List<ITransferRule> rules = new List<ITransferRule>();
		public List<ITransferRule> Rules { get { return rules; } }

		public readonly List<TransferMap> maps = new List<TransferMap>();

		public IEnumerable<IGrouping<String, ITransferGrammarItem>> ItemGroups
		{
			get
			{
				yield return new Grouping<String, ITransferGrammarItem>("rules", rules);
				yield return new Grouping<String, ITransferGrammarItem>("maps", maps);
			}
		}

		public static String clean_grammar_key(String key)
		{
			while (true)
			{
				key = key.Trim();
				if (key.Length == 0)
					return null;
				if (key[key.Length - 1] != ':')
					break;
				key = key.Substring(0, key.Length - 1);
			}
			return key.ToLower();
		}

		public GrammarRef this[String key] { get { return _get_grammar(key, false); } }

		GrammarRef _get_grammar(String key, bool f_error)
		{
			GrammarRef tgr;
			if (grammar_refs != null && (key = clean_grammar_key(key)) != null)
				for (int i = 0; i < grammar_refs.Length; i++)
					if ((tgr = grammar_refs[i]) != null && tgr.key == key)
						return tgr;
			if (!f_error)
				return null;
			throw new TransferGrammarLoadException("Grammar key '{0}' was not found in transfer grammar '{1}'", key, this.Name);
		}

		public int GetGrammarIndex(Grammar g)
		{
			for (int i = 0; i < grammar_refs.Length; i++)
				if (grammar_refs[i].g == g)
					return i;
			return -1;
		}

		public GrammarRef this[Grammar g]
		{
			get
			{
				GrammarRef tgr;
				for (int i = 0; i < grammar_refs.Length; i++)
					if ((tgr = grammar_refs[i]).g == g)
						return tgr;
				return default(GrammarRef);
			}
		}
		/// <summary>
		/// from global grammar index
		/// </summary>
		public GrammarRef this[int ggix]
		{
			get { return this[Grammar.GetGrammar(ggix)]; }
		}

		public GrammarRef this[TaggedRelMode rmg]
		{
			get { return this[Grammar.GetGrammar(rmg.grm_idx)]; }
		}

		public TransferMap GetTransferMap(String map_key)
		{
			return maps.FirstOrDefault(_mm => String.Compare(_mm.Name, map_key.Trim(), true) == 0);
		}

		public override String ToString()
		{
			return String.Format("{0}", Name);
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	public class TransferGrammarResource : TransferGrammar
	{
		struct Keyword
		{
			public const String grammars = "grammars";
			public const String name = "name";
			public const String description = "description";
			public const String author = "author";
			public const String date = "date";
			public const String priority_maps = "priority-maps";
			public const String multi_maps = "multi-maps";
			public const String rule_groups = "rule-groups";
			public static bool IsKeyword(String s)
			{
				s = s.ToLowerInvariant();
				return s == grammars || s == name || s == description || s == author || s == date ||
						s == priority_maps || s == multi_maps || s == rule_groups;
			}
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// load transfer grammar description from a textual 'xdl' description
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public TransferGrammarResource(IRtParent parent, String filename)
			: base(parent, filename)
		{
			var sections = get_sections(get_source_xdl());

			//this.author_info = new AuthorInfo
			//{
			Name = (sections.TryGetValue(Keyword.name) ?? String.Empty).Trim();
			//Description = (sections.TryGetValue(Keyword.description) ?? String.Empty).Trim();
			//Author = (sections.TryGetValue(Keyword.author) ?? String.Empty).Trim();
			//Date = (sections.TryGetValue(Keyword.date) ?? String.Empty).Trim();
			//};

			flag_name_map = new Dictionary<string, int>();
			label_name_map = new Dictionary<string, int>();

			/// sections can appear in any order in the transfer grammar description file. sections which aren't 
			/// introduced by a keyword are transfer rule descriptions.

			/// 1. 'grammars' section is mandatory
			String s_sect;
			if (!sections.TryGetValue(Keyword.grammars, out s_sect))
				throw new Exception("no grammars section");
			LoadGrammars(s_sect);

			/// 2. 'priority-maps' (optional)
			if (sections.TryGetValue(Keyword.priority_maps, out s_sect))
				LoadPriorityMaps(s_sect);

			/// 3. 'multi-maps' (optional)
			if (sections.TryGetValue(Keyword.multi_maps, out s_sect))
				LoadMultiMaps(s_sect);

			/// 4. rules.
			foreach (var kvp in sections.Where(s => !Keyword.IsKeyword(s.Key)))
			{
				ITransferRule r = TransferRule.Create(parent, this, kvp.Key, kvp.Value);
				rules.Add(r);
			}

			/// 5. 'rule-groups' (optional)
			if (sections.TryGetValue(Keyword.rule_groups, out s_sect))
				LoadRuleGroups(s_sect);
		}

		void LoadPriorityMaps(String s_pm_sect)
		{
			foreach (var kvp in get_sections(s_pm_sect))
			{
				if (maps.Any(_pm => String.Compare(_pm.Name, kvp.Key, true) == 0))
					throw new Exception();
				PriorityMap pm = new PriorityMap(this, kvp.Key, kvp.Value);
				maps.Add(pm);
			}
		}

		void LoadMultiMaps(String s_mm_sect)
		{
			foreach (var kvp in get_sections(s_mm_sect))
			{
				if (GetTransferMap(kvp.Key) != null)
					throw new Exception("a map with name {0} has already been defined");
				MultiMap mm = new MultiMap(this, kvp.Key, kvp.Value);
				maps.Add(mm);
			}
		}

		void LoadRuleGroups(String s_rg_sect)
		{
			foreach (var kvp in get_sections(s_rg_sect))
			{
				if (rules.Any(_r => String.Compare(_r.Name, kvp.Key, true) == 0))
					throw new TransferGrammarLoadException("rule group specifies rule name '{0}' which is already defined", kvp.Key);
				TransferRuleGroup rg = TransferRuleGroup.Create(this, kvp.Key, kvp.Value);
				if (rg.Count > 0)
					rules.Add(rg);
			}
		}

		void LoadGrammars(String s_grm_sect)
		{
			String[] rg_gs = s_grm_sect.Split(',');
			if (rg_gs.Length != 2)
				throw new Exception("Transfer grammar must specifyexactly 2 grammars");

			GrammarKeyName gkn0 = _crack_grammar_identifier(rg_gs[0]);
			GrammarKeyName gkn1 = _crack_grammar_identifier(rg_gs[1]);

			if (String.Compare(gkn0.s_key, gkn1.s_key, true) == 0)
				throw new TransferGrammarLoadException("Grammar key names must be unique in a transfer grammar");
			/// note: but I don't see a reason to necessarily exclude: ( en1:erg en2:erg )

			grammar_refs[0] = AddGrammar(gkn0);
			grammar_refs[1] = AddGrammar(gkn1);
		}

		struct GrammarKeyName
		{
			public String s_key;
			public String s_name;
		};

		GrammarKeyName _crack_grammar_identifier(String s_in)
		{
			String[] kvp = s_in.Split(Charset.colon, StringSplitOptions.RemoveEmptyEntries);
			if (kvp.Length != 2)
				throw new Exception("improper grammar identifier");
			return new GrammarKeyName { s_key = clean_grammar_key(kvp[0]), s_name = kvp[1].Trim() };
		}


		GrammarRef AddGrammar(GrammarKeyName gkn)
		{
			if (this[gkn.s_key] != null)
				throw new TransferGrammarLoadException("Transfer grammar already defines a key '{0}'", gkn.s_key);

			Grammar g = Grammar.FindGrammar(gkn.s_name);
			if (g == null)
				throw new TransferGrammarLoadException("Transfer grammar could not find referenced grammar '{0}'", gkn.s_name);

			return new GrammarRef(this, g, gkn.s_key);
		}


		Dictionary<String, String> get_sections(String s)
		{
			Dictionary<String, String> sections = new Dictionary<String, String>(StringComparer.InvariantCultureIgnoreCase);
			foreach (var kvp in _enum_sections(s))
			{
				if (sections.ContainsKey(kvp.Key))
					throw new TransferGrammarLoadException("section '{0}' already defined", kvp.Key);
				sections.Add(kvp.Key, kvp.Value);
			}
			return sections;
		}

		IEnumerable<KeyValuePair<String, String>> _enum_sections(String s)
		{
			StringBuilder sb_pre = new StringBuilder();
			StringBuilder sb_cont = new StringBuilder();
			int brace_nest = 0;
			for (int i = 0; i < s.Length; i++)
			{
				Char ch = s[i];
				if (ch == '}')
				{
					if (brace_nest == 0)
						throw new Exception();
					if (--brace_nest == 0)
					{
						String s_pre = sb_pre.ToString().Trim();
						String s_cont = sb_cont.ToString().Trim();
						//Debug.Print("[{0}] [{1}]", s_pre, s_cont);

						if (s_pre.Length == 0)
							throw new TransferGrammarLoadException("no keyword to introduce  '{{ {0} }}'", s_cont);
						if (s_pre[s_pre.Length - 1] != ':')
							throw new Exception(s_pre + " should be followed by ':'");
						s_pre = s_pre.Substring(0, s_pre.Length - 1).TrimEnd();
						if (s_pre.Contains(' '))
							throw new TransferGrammarLoadException("'{0}' cannot contain spaces", s_pre);
						yield return new KeyValuePair<String, String>(s_pre, s_cont);

						sb_pre.Clear();
						sb_cont.Clear();
						continue;
					}
				}
				else if (ch == '{')
				{
					if (++brace_nest == 1)
						continue;
				}
				if (brace_nest == 0)
					sb_pre.Append(ch);
				else
					sb_cont.Append(ch);
			}
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class SrcTgtGrammar : TransferGrammar
	{
		public SrcTgtGrammar(IIdentity prv, GrammarRef source, GrammarRef target)
			: base(prv, source.tg.filename)
		{
			if (source.tg != target.tg)
				throw new Exception("GrammarRefs must refer to the same transfer grammar");

			alib.Upcast.Clone<TransferGrammar, SrcTgtGrammar>(source.tg, this);

			this.Source = source;
			this.Target = target;
		}

		//public IList<SrcTgtXRule> Rules { get { return null; } }

		public GrammarRef Source;
		public GrammarRef Target;
		public TokenizedStringTransfer tok_str_xfer;
		public SurfaceStringTransfer surf_str_xfer;
	};
}
